/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "gamePCH.h"
/* ScriptData
 SDName: Npc_EscortAI
 SD%Complete: 100
 SDComment:
 SDCategory: Npc
 EndScriptData */

#include "ScriptPCH.h"
#include "ScriptedEscortAI.h"
#include "Group.h"

enum ePoints {
	POINT_LAST_POINT = 0xFFFFFF, POINT_HOME = 0xFFFFFE
};

npc_escortAI::npc_escortAI(Creature* pCreature) :
		ScriptedAI(pCreature), m_uiPlayerGUID(0), m_uiWPWaitTimer(2500), m_uiPlayerCheckTimer(
				1000), m_uiEscortState(STATE_ESCORT_NONE), MaxPlayerDistance(
				DEFAULT_MAX_PLAYER_DISTANCE), m_pQuestForEscort(NULL), m_bIsActiveAttacker(
				true), m_bIsRunning(false), m_bCanInstantRespawn(false), m_bCanReturnToStart(
				false), DespawnAtEnd(true), DespawnAtFar(true), ScriptWP(false) {
}

void npc_escortAI::AttackStart(Unit* pWho) {
	if (!pWho)
		return;

	if (me->Attack(pWho, true)) {
		if (me->GetMotionMaster()->GetCurrentMovementGeneratorType()
				== POINT_MOTION_TYPE)
			me->GetMotionMaster()->MovementExpired();

		if (IsCombatMovement())
			me->GetMotionMaster()->MoveChase(pWho);
	}
}

//see followerAI
bool npc_escortAI::AssistPlayerInCombat(Unit* pWho) {
	if (!pWho || !pWho->getVictim())
		return false;

	//experimental (unknown) flag not present
	if (!(me->GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_AID_PLAYERS))
		return false;

	//not a player
	if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
		return false;

	//never attack friendly
	if (me->IsFriendlyTo(pWho))
		return false;

	//too far away and no free sight?
	if (me->IsWithinDistInMap(pWho, GetMaxPlayerDistance())
			&& me->IsWithinLOSInMap(pWho)) {
		//already fighting someone?
		if (!me->getVictim()) {
			AttackStart(pWho);
			return true;
		} else {
			pWho->SetInCombatWith(me);
			me->AddThreat(pWho, 0.0f);
			return true;
		}
	}

	return false;
}

void npc_escortAI::MoveInLineOfSight(Unit* pWho) {
	if (!me->HasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack()
			&& pWho->isInAccessiblePlaceFor(me)) {
		if (HasEscortState(STATE_ESCORT_ESCORTING)
				&& AssistPlayerInCombat(pWho))
			return;

		if (!me->canFly() && me->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
			return;

		if (me->IsHostileTo(pWho)) {
			float fAttackRadius = me->GetAttackDistance(pWho);
			if (me->IsWithinDistInMap(pWho, fAttackRadius)
					&& me->IsWithinLOSInMap(pWho)) {
				if (!me->getVictim()) {
					pWho->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
					AttackStart(pWho);
				} else if (me->GetMap()->IsDungeon()) {
					pWho->SetInCombatWith(me);
					me->AddThreat(pWho, 0.0f);
				}
			}
		}
	}
}

void npc_escortAI::JustDied(Unit* /*pKiller*/) {
	if (!HasEscortState(STATE_ESCORT_ESCORTING) || !m_uiPlayerGUID
			|| !m_pQuestForEscort)
		return;

	if (Player* pPlayer = GetPlayerForEscort()) {
		if (Group* pGroup = pPlayer->GetGroup()) {
			for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL;
					pRef = pRef->next()) {
				if (Player* pMember = pRef->getSource()) {
					if (pMember->GetQuestStatus(m_pQuestForEscort->GetQuestId())
							== QUEST_STATUS_INCOMPLETE)
						pMember->FailQuest(m_pQuestForEscort->GetQuestId());
				}
			}
		} else {
			if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId())
					== QUEST_STATUS_INCOMPLETE)
				pPlayer->FailQuest(m_pQuestForEscort->GetQuestId());
		}
	}
}

void npc_escortAI::JustRespawned() {
	m_uiEscortState = STATE_ESCORT_NONE;

	if (!IsCombatMovement())
		SetCombatMovement(true);

	//add a small delay before going to first waypoint, normal in near all cases
	m_uiWPWaitTimer = 2500;

	if (me->getFaction() != me->GetCreatureInfo()->faction_A)
		me->RestoreFaction();

	Reset();
}

void npc_escortAI::ReturnToLastPoint() {
	float x, y, z, o;
	me->GetHomePosition(x, y, z, o);
	me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z);
}

void npc_escortAI::EnterEvadeMode() {
	me->RemoveAllAuras();
	me->DeleteThreatList();
	me->CombatStop(true);
	me->SetLootRecipient(NULL);

	if (HasEscortState(STATE_ESCORT_ESCORTING)) {
		AddEscortState(STATE_ESCORT_RETURNING);
		ReturnToLastPoint();
		sLog->outDebug(
				LOG_FILTER_TSCR,
				"TSCR: EscortAI (script: %s, creature entry: %u) has left combat and is now returning to last point");
	} else {
		me->GetMotionMaster()->MoveTargetedHome();
		Reset();
	}
}

bool npc_escortAI::IsPlayerOrGroupInRange() {
	if (Player* pPlayer = GetPlayerForEscort()) {
		if (Group* pGroup = pPlayer->GetGroup()) {
			for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL;
					pRef = pRef->next()) {
				Player* pMember = pRef->getSource();

				if (pMember
						&& me->IsWithinDistInMap(pMember,
								GetMaxPlayerDistance())) {
					return true;
					break;
				}
			}
		} else {
			if (me->IsWithinDistInMap(pPlayer, GetMaxPlayerDistance()))
				return true;
		}
	}
	return false;
}

void npc_escortAI::UpdateAI(const uint32 uiDiff) {
	//Waypoint Updating
	if (HasEscortState(STATE_ESCORT_ESCORTING) && !me->getVictim()
			&& m_uiWPWaitTimer && !HasEscortState(STATE_ESCORT_RETURNING)) {
		if (m_uiWPWaitTimer <= uiDiff) {
			//End of the line
			if (CurrentWP == WaypointList.end()) {
				if (DespawnAtEnd) {
					sLog->outDebug(LOG_FILTER_TSCR,
							"TSCR: EscortAI reached end of waypoints");

					if (m_bCanReturnToStart) {
						float fRetX, fRetY, fRetZ;
						me->GetRespawnCoord(fRetX, fRetY, fRetZ);

						me->GetMotionMaster()->MovePoint(POINT_HOME, fRetX,
								fRetY, fRetZ);

						m_uiWPWaitTimer = 0;

						sLog->outDebug(
								LOG_FILTER_TSCR,
								"TSCR: EscortAI are returning home to spawn location: %u, %f, %f, %f",
								POINT_HOME, fRetX, fRetY, fRetZ);
						return;
					}

					if (m_bCanInstantRespawn) {
						me->setDeathState(JUST_DIED);
						me->Respawn();
					} else
						me->ForcedDespawn();

					return;
				} else {
					sLog->outDebug(
							LOG_FILTER_TSCR,
							"TSCR: EscortAI reached end of waypoints with Despawn off");

					return;
				}
			}

			if (!HasEscortState(STATE_ESCORT_PAUSED)) {
				me->GetMotionMaster()->MovePoint(CurrentWP->id, CurrentWP->x,
						CurrentWP->y, CurrentWP->z);
				sLog->outDebug(LOG_FILTER_TSCR,
						"TSCR: EscortAI start waypoint %u (%f, %f, %f).",
						CurrentWP->id, CurrentWP->x, CurrentWP->y,
						CurrentWP->z);

				WaypointStart(CurrentWP->id);

				m_uiWPWaitTimer = 0;
			}
		} else
			m_uiWPWaitTimer -= uiDiff;
	}

	//Check if player or any member of his group is within range
	if (HasEscortState(STATE_ESCORT_ESCORTING) && m_uiPlayerGUID
			&& !me->getVictim() && !HasEscortState(STATE_ESCORT_RETURNING)) {
		if (m_uiPlayerCheckTimer <= uiDiff) {
			if (DespawnAtFar && !IsPlayerOrGroupInRange()) {
				sLog->outDebug(
						LOG_FILTER_TSCR,
						"TSCR: EscortAI failed because player/group was to far away or not found");

				if (m_bCanInstantRespawn) {
					me->setDeathState(JUST_DIED);
					me->Respawn();
				} else
					me->ForcedDespawn();

				return;
			}

			m_uiPlayerCheckTimer = 1000;
		} else
			m_uiPlayerCheckTimer -= uiDiff;
	}

	UpdateEscortAI(uiDiff);
}

void npc_escortAI::UpdateEscortAI(const uint32 /*uiDiff*/) {
	if (!UpdateVictim())
		return;

	DoMeleeAttackIfReady();
}

void npc_escortAI::MovementInform(uint32 uiMoveType, uint32 uiPointId) {
	if (uiMoveType != POINT_MOTION_TYPE
			|| !HasEscortState(STATE_ESCORT_ESCORTING))
		return;

	//Combat start position reached, continue waypoint movement
	if (uiPointId == POINT_LAST_POINT) {
		sLog->outDebug(
				LOG_FILTER_TSCR,
				"TSCR: EscortAI has returned to original position before combat");

		if (m_bIsRunning && me->HasUnitMovementFlag(MOVEMENTFLAG_WALKING))
			me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING);
		else if (!m_bIsRunning
				&& !me->HasUnitMovementFlag(MOVEMENTFLAG_WALKING))
			me->AddUnitMovementFlag(MOVEMENTFLAG_WALKING);

		RemoveEscortState(STATE_ESCORT_RETURNING);

		if (!m_uiWPWaitTimer)
			m_uiWPWaitTimer = 1;
	} else if (uiPointId == POINT_HOME) {
		sLog->outDebug(
				LOG_FILTER_TSCR,
				"TSCR: EscortAI has returned to original home location and will continue from beginning of waypoint list.");

		CurrentWP = WaypointList.begin();
		m_uiWPWaitTimer = 1;
	} else {
		//Make sure that we are still on the right waypoint
		if (CurrentWP->id != uiPointId) {
			sLog->outError(
					"TSCR ERROR: EscortAI reached waypoint out of order %u, expected %u, creature entry %u",
					uiPointId, CurrentWP->id, me->GetEntry());
			return;
		}

		sLog->outDebug(LOG_FILTER_TSCR, "TSCR: EscortAI Waypoint %u reached",
				CurrentWP->id);

		//Call WP function
		WaypointReached(CurrentWP->id);

		m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1;

		++CurrentWP;
	}
}

/*
 void npc_escortAI::OnPossess(bool apply)
 {
 // We got possessed in the middle of being escorted, store the point
 // where we left off to come back to when possess is removed
 if (HasEscortState(STATE_ESCORT_ESCORTING))
 {
 if (apply)
 me->GetPosition(LastPos.x, LastPos.y, LastPos.z);
 else
 {
 Returning = true;
 me->GetMotionMaster()->MovementExpired();
 me->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z);
 }
 }
 }
 */

void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z,
		uint32 WaitTimeMs) {
	Escort_Waypoint t(id, x, y, z, WaitTimeMs);

	WaypointList.push_back(t);

	// i think SD2 no longer uses this function
	ScriptWP = true;
	/*PointMovement wp;
	 wp.m_uiCreatureEntry = me->GetEntry();
	 wp.m_uiPointId = id;
	 wp.m_fX = x;
	 wp.m_fY = y;
	 wp.m_fZ = z;
	 wp.m_uiWaitTime = WaitTimeMs;
	 PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/
}

void npc_escortAI::FillPointMovementListForCreature() {
	std::vector<ScriptPointMove> const &pPointsEntries =
			sScriptSystemMgr->GetPointMoveList(me->GetEntry());

	if (pPointsEntries.empty())
		return;

	std::vector<ScriptPointMove>::const_iterator itr;

	for (itr = pPointsEntries.begin(); itr != pPointsEntries.end(); ++itr) {
		Escort_Waypoint pPoint(itr->uiPointId, itr->fX, itr->fY, itr->fZ,
				itr->uiWaitTime);
		WaypointList.push_back(pPoint);
	}
}

void npc_escortAI::SetRun(bool bRun) {
	if (bRun) {
		if (!m_bIsRunning)
			me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING);
		else
			sLog->outDebug(
					LOG_FILTER_TSCR,
					"TSCR: EscortAI attempt to set run mode, but is already running.");
	} else {
		if (m_bIsRunning)
			me->AddUnitMovementFlag(MOVEMENTFLAG_WALKING);
		else
			sLog->outDebug(
					LOG_FILTER_TSCR,
					"TSCR: EscortAI attempt to set walk mode, but is already walking.");
	}
	m_bIsRunning = bRun;
}

//TODO: get rid of this many variables passed in function.
void npc_escortAI::Start(bool bIsActiveAttacker, bool bRun, uint64 uiPlayerGUID,
		const Quest* pQuest, bool bInstantRespawn, bool bCanLoopPath) {
	if (me->getVictim()) {
		sLog->outError(
				"TSCR ERROR: EscortAI (script: %s, creature entry: %u) attempts to Start while in combat",
				me->GetScriptName().c_str(), me->GetEntry());
		return;
	}

	if (HasEscortState(STATE_ESCORT_ESCORTING)) {
		sLog->outError(
				"TSCR: EscortAI (script: %s, creature entry: %u) attempts to Start while already escorting",
				me->GetScriptName().c_str(), me->GetEntry());
		return;
	}

	if (!ScriptWP) // sd2 never adds wp in script, but tc does
	{
		if (!WaypointList.empty())
			WaypointList.clear();
		FillPointMovementListForCreature();
	}

	if (WaypointList.empty()) {
		sLog->outErrorDb(
				"TSCR: EscortAI (script: %s, creature entry: %u) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: %u).",
				me->GetScriptName().c_str(), me->GetEntry(),
				pQuest ? pQuest->GetQuestId() : 0);
		return;
	}

	//set variables
	m_bIsActiveAttacker = bIsActiveAttacker;
	m_bIsRunning = bRun;

	m_uiPlayerGUID = uiPlayerGUID;
	m_pQuestForEscort = pQuest;

	m_bCanInstantRespawn = bInstantRespawn;
	m_bCanReturnToStart = bCanLoopPath;

	if (m_bCanReturnToStart && m_bCanInstantRespawn)
		sLog->outDebug(
				LOG_FILTER_TSCR,
				"TSCR: EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn.");

	if (me->GetMotionMaster()->GetCurrentMovementGeneratorType()
			== WAYPOINT_MOTION_TYPE) {
		me->GetMotionMaster()->MovementExpired();
		me->GetMotionMaster()->MoveIdle();
		sLog->outDebug(
				LOG_FILTER_TSCR,
				"TSCR: EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle.");
	}

	//disable npcflags
	me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);

	sLog->outDebug(
			LOG_FILTER_TSCR,
			"TSCR: EscortAI started with " UI64FMTD " waypoints. ActiveAttacker = %d, Run = %d, PlayerGUID = " UI64FMTD "",
			uint64(WaypointList.size()), m_bIsActiveAttacker, m_bIsRunning,
			m_uiPlayerGUID);

	CurrentWP = WaypointList.begin();

	//Set initial speed
	if (m_bIsRunning)
		me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING);
	else
		me->AddUnitMovementFlag(MOVEMENTFLAG_WALKING);

	AddEscortState(STATE_ESCORT_ESCORTING);
}

void npc_escortAI::SetEscortPaused(bool bPaused) {
	if (!HasEscortState(STATE_ESCORT_ESCORTING))
		return;

	if (bPaused)
		AddEscortState(STATE_ESCORT_PAUSED);
	else
		RemoveEscortState(STATE_ESCORT_PAUSED);
}
